package com.zxy.supplier_system.server.utils

import jakarta.persistence.criteria.*
import org.springframework.data.jpa.domain.Specification


typealias PathBuilder<E, X> = (root: Root<E>) -> Path<X>
typealias FromBuilder<E, X, T> = (root: Root<E>) -> From<X, T>

object SpecificationUtils {

    fun <E> empty(): Specification<E> {
        return Specification.where(null)
    }

    fun <E> contains(pathBuilder: PathBuilder<E, String>, pattern: String?): Specification<E> {
        if (pattern.isNullOrBlank()) {
            return empty()
        }
        return Specification<E> { root, _, criteriaBuilder ->
            criteriaBuilder.like(pathBuilder(root), "%$pattern%")
        }
    }

    fun <E> `in`(pathBuilder: PathBuilder<E, out Any>, collection: Collection<*>): Specification<E> {
        if (collection.isEmpty()) {
            return empty()
        }
        return Specification<E> { root, _, criteriaBuilder ->
            val `in` = criteriaBuilder.`in`(pathBuilder(root))
            collection.forEach {
                `in`.value(it)
            }
            `in`
        }
    }


    fun <X, Y> join(
        criteriaRoot: From<*, X>,
        attribute: String,
        joinType: JoinType = JoinType.INNER
    ): Join<X, Y> {
        @Suppress("UNCHECKED_CAST")
        return criteriaRoot.joins.stream()
            .filter { j: Join<X, *> ->
                j.attribute.name == attribute && j.joinType == joinType
            }
            .findFirst()
            .orElseGet {
                criteriaRoot.join<X, Y>(
                    attribute,
                    joinType
                )
            } as Join<X, Y>
    }

    fun <E> nullStatusByBoolean(pathBuilder: PathBuilder<E, out Any>, boolean: Boolean?): Specification<E> {
        if (boolean == null) {
            return empty()
        }
        return Specification<E> { root, _, criteriaBuilder ->
            (if (boolean) {
                criteriaBuilder.isNotNull(pathBuilder(root))
            } else {
                criteriaBuilder.isNull(pathBuilder(root))
            })
        }
    }


}

internal fun <X, Y> From<*, X>.joinOnce(
    attribute: String,
    joinType: JoinType = JoinType.INNER
): Join<X, Y> {
    @Suppress("UNCHECKED_CAST")
    return this.joins.stream()
        .filter { j: Join<X, *> ->
            j.attribute.name == attribute && j.joinType == joinType
        }
        .findFirst()
        .orElseGet {
            this.join<X, Y>(
                attribute,
                joinType
            )
        } as Join<X, Y>
}